/*
	
	Froth User Interface Kit
	===================================

	Requires: froth.core.js, jquery.1.3.js, jsdate
	
	Created: Tues, Oct 20, 2009
	Created By: Allan Phillips
	
	@copyright 2009 Thinking Code Software. All Rights Reserved
	
	Released under the MIT License
	
	Changes:
	1.0.0 	- Oct 21, 2009 	- Implemented NSView
	1.0.1	- Oct 24, 2009	- Added View bindings ability with bind(attribute, object, key) for binding to other NSObjects
	
	Documentation
	=====================================
	
	Documentation to come.
	
*/

// !WBResponder
/*

	WBResponder
	========================
	
	Provides event handling and responder chain support 
	
	This is mostly modeled after OpenStep's NSResponder, but suited to dom events
	
*/
fClass.NSObject.extend("WBResponder", { ivars: {
	nextResponder:null
}, methods:{

init:function() {
	if(this.performSuper(this, "init")) {
		/* 	Dynamic checking for methods to setup event bindings, this
			insures that unneccessary event listeners are not added for the responder
		*/
		
		var that = this;
		
		//Each event methods is defined here, subclasses can add impl if needed
		if(this.dom) {
			if(this.insertNewline || this.keyUp) {
				this.dom.keyup(function(e){
					if(e.keyCode == 13) {
						that.insertNewline(e);
					} 
					
					//This handles both so impl one. enables other
					if(this.keyUp) {
						that.keyUp(e);
					}	
				});
			}
			
			if(this.becomeFirstResponder) {
				this.dom.focus(function(e) {
					that.becomeFirstResponder(e);
				});
			}
			
			if(this.resignFirstResponder) {
				this.dom.blur(function(e) {
					that.resignFirstResponder(e);
				});
			}
			
			//-----Mouse Events-------
			if(this.mouseEntered) {
				this.dom.mouseenter(function(e) {
					that.mouseEntered(e);
				});
			}
			
			if(this.mouseExited) {
				this.dom.mouseleave(function(e) {
					that.mouseExited(e);
				});
			}
			
			//Window events/resize events
			if(this.windowLiveResize) {
				$(window).resize(function(e) {
					that.windowLiveResize(e);
				});
			}
		}
	}
	return this;
},

//TODO: Implement responder like events

setNextResponder:function(responder) {
	this.nextResponder = responder;
	//...todo...
}, 

nextResponder:function() {
	return this.nextResponder;
	//... todo...
},

/*
	Use this method, never becomeFirstResponder to make the view the first responder
*/
makeFirstResponder:function() {
	this.dom.focus();
}

}});

/*

	FFFrameMake
	------------------------
	
	Convience function for create setFrame() compatible frame stuctures
	
*/
FFrameMake = function(x,y,width,height) {
	return {
		origin:{
			x:x, 
			y:y
		}, 
		size:{
			width:width, 
			height:height
		}
	};
}

/*

	WBColor
	------------------------
	
 	Provides color conversion and representation for FRViews and drawing methods/classes 
 	
*/
fClass.NSObject.extend("WBColor", { ivars: {
	// .. no ivars ..
}, methods: {

//Main init, alpha is optional. values are in decimal 0-1.0
init:function(red, green, blue, alpha) {
	if(this.performSuper(this, 'init')) {
		this.red = red;
		this.green = green;
		this.blue = blue;
		if(alpha)
			this.alpha = alpha;
	
	}
	return this;
},
	
cssValue:function() {
	if(this.alpha) {
		return "rgba("+Math.round(this.red*255)+", "+Math.round(this.green*255)+", "+Math.round(this.blue*255)+", "+Math.round(this.alpha*255)+")";
	}
	return "rgb("+Math.round(this.red*255)+", "+Math.round(this.green*255)+", "+Math.round(this.blue*255)+")";
}

}});

// !WBView
/*
	
	WBView
	========================
	
	All application views and dom elements can be displayed as a view (or as a NSCell)
	
	
	
	View CSS Classes
	=========================
	
	View and all subclass can be styled by using ther namespace names as css classes. Each view and subclass is given a class name programically defined
	from its class name and heirichy.
 
	For example a view class FRMyCoolView : FRButton : FRView
		css-class is: .f_view_button_my_cool_view.
	
	Notice that names are reverse camelcased using _ as seperator
 
	Of course custom class can also be added with 
	self.addCSSClass("classname");
	
*/
fClass.WBResponder.extend("WBView", { ivars:{

}, methods: {

/*
 	Designated Initalizer
 	-----------------------
 	
 	-tagOrDiv, the <tag> for the dom element, if null, then <div>
 	-idOrNull, the unique id for the dom element, ie <div id='{idOrNull'></div>
*/
initWithTag:function(tagOrDiv, idOrNull) {
	//Add the workspace jquary
	/*
		This is done first as NSResponder needs a dom ivar to work with
	*/
	this.tag = tagOrDiv?tagOrDiv:"div";
	this.dom = $("<"+this.tag+"/>");
	
	this.performSuper(this, "init");
	
	
	if(idOrNull) {
		this.uid = idOrNull;
	}
	
	//Add empty subviews array
	this.subviews = [];
	
	//Add null superview
	this.superview = null;	
	
	//Subclasses may remove this in their init
	//this.dom.addClass(this.styleNameFromNamespace());
	
	/** TEST **/
	//this.draw();
	
	return this;
},

//Needed to provide a real duplicate of a jquery dom.
mutableCopy:function() {
	var cp = this.performSuper(this, 'mutableCopy');
	cp.dom = this.dom.clone();
	return cp;
},

/*init:function() {
	this.initWithTag("div", "f_view_"+(new Date()).getTime().toString());
	return this;
},*/

//Subclass overides for functionality
flipped:function() {
	return false;
},

//Information Methods
/*
	returns generated class, that all divs get, based on the view name, inheratance.
	ie for NSThisView:NSThatView:NSView => <div class='f_view_this_view_that_view'/>
*/
styleNameFromNamespace:function() {
	return this.fnamespace().replace(/\./g, "_").replace("object_responder_view", "f_view");
},

addCSSClass:function(name) {
	this.dom.addClass(name);
},

removeCSSClass:function(name) {
	this.dom.removeClass(name);
},

//Sets the dom context's id attribute
setId:function(anId) {
	this.dom.attr("id", anId);
	this.uid = anId;
},

id:function() {
	return this.dom.attr("id");	//yes, yes we could do this.dom[0].id but what fun is that.
},

//Subview management

/*
	Adds a subview to the view that will get rendered with the view in -drawing().
	
	If the view has an active display context (ie dom is injected into document, then this
	will also perform drawing of subview
	
	This also sets the receiver as the next responder for subview (See WBResponder for details)
	
	-animation, optional animation for showing when context is not null, fade/slide
	-opts, optional options for the animation
	-callback, optional callback executed when the show animation completes
*/
addSubview:function(subview, animation, opts, callback) {
	subview.superview = this;
	this.subviews.push(subview);
	
	//Setup the z-index for the added object
	//this.dom[0].style.zIndex = indexof(subview, this.subviews)+10;
	
	//Responder chain managment, (See WBResponder)
 	subview.setNextResponder(this);
	
	//Handle if we have a drawing context this could be onScreen of offscreen
	if(this.dom.parent().length>0) {
		
		var doContextChanges = false;
		if(subview.dom.parent() != this.dom) {
			subview.contextWillChange(this.dom);
			doContextChanges = true;
		}
	
		this.dom.append(subview.drawing());
		
		if(doContextChanges) {
			subview.contextDidChange(this.dom);
		}
		
		if(animation && animation == 'fade') {
			subview.dom.fadeIn(opts?opts:100, callback);
		} else if(animation && animation == 'slide') {
			subview.dom.slideDown(opts?opts:100, callback);
		} else {
			subview.dom.show();
		}
	}
	
	return this;	//adds chaining support
},

removeSubview:function(subview) {
	subview.superview = null;
	this.subviews = removefrom(subview, this.subviews);
	
	//Remove backing dom context
 	subview.dom.remove();
	
	return this;
},

removeAllSubviews:function() {
	this.dom.contents().remove();
	/*for(var i in this.subviews) {
		this.subviews[i].dom.remove();
	}*/
	this.subviews = [];
},

hideAllSubviews:function(animation, opts, callback) {
	for(var i in this.subviews) {
		this.subviews[i].hide(arguments);
	}
},

removeFromSuperview:function(animation, opts) {
	
	if(this.superview) {
		this.superview.subviews = removefrom(this, this.superview.subviews);
		this.superview = null;
	}
	
	//Remove backing dom context if self, or parent -drawing() has been called
	if(animation) {
		if(animation === 'fade') {
			this.dom.fadeOut(opts, function() {
				$(this).remove();
			});
		} else if(animation === 'slide') {
			this.dom.slideUp(opts, function() {
				$(this).remove();
			});
		}
	} else {
		this.dom.remove();
	}
	return this;
	
},

//Geometery

/*
	Remember that css positioning require's that superview's position is relative or absolute as well,
	the following methods automatically do that if not already set by super, and the receiver already
	has a superview
*/
setAbsolute:function() {
	//Require's superview's dom to be relative by css rules
	if(this.superview) {
		if(!this.superview.positioning() || this.superview.positioning() == 'static') {
			this.superview.setRelative();
		}
	}
	return this.dom.css("position", "absolute");
},

setRelative:function() {
	//Require's superview's dom to be relative by css rules
	if(this.superview) {
		if(!this.superview.positioning() || this.superview.positioning() == 'static') {
			this.superview.setRelative();
		}
	}
	return this.dom.css("position", "relative");
},

positioning:function() {
	return this.dom.css("position");
},

//Floating
setFloatLeft:function() {
	return this.dom.css("float", "left");
},

setFloatRight:function() {
	return this.dom.css("float", "right");
},

/*
	Hiding and showing options
	-------------------------
*/
show:function(animation, opts, func) {
	if(animation) {
		if(animation === 'fade') {
			this.dom.fadeIn(opts, func);
		} else if(animation === 'slide') {
			this.dom.slideDown(opts, func);
		}
	} else {
		this.dom.show();
	}
	
	this._isHidden = false;
},

hide:function(animation, opts, func) {
	if(animation) {
		if(animation === 'fade') {
			this.dom.fadeOut(opts, func);
		} else if(animation === 'slide') {
			this.dom.slideUp(opts, func);
		}
	} else {
		this.dom.hide();
	}
	
	this._isHidden = true;
},

float:function() {
	return this.dom.css("float");
},

/*
	Indicates that the view can draw out of its bounds ie .css("overflow", "hidden");
*/
setDrawsOutOfBounds:function() {
	this.dom.css("overflow", "");
	this._drawsOutOfBounds = true;
},

/*	Sets the size for the view, sizes are simply a object with { origin:{x=|left|right:23, y|bottom|top:13} size:{width:XX, height:XX}  }
	All froth views if using x/y is according to -isFlipped property that subclasses can overide
	
	Frame object definition
	======================
	{ 	origin: {
			x:{int}, or left/right:{int}
			y:{int}, or top/bottom:{int}
		},
		size: {
			width:{int} or null for "auto".
			height:{int} or null for "auto".
		}
	}
	
	Any comination or single value can be used to set a single, ie
	setFrame({ size: { height:123 } }) would just change the height

*/
setFrame:function(frame) {
	// !TODO: fixme, what if frame setter is a single, or part?
	this.frame = frame; 
		
	//Now set the actual layout for the frame.
	if(frame.origin) {
		if(this.isFlipped && frame.origin.y !== null) {
			this.dom.css("top", frame.origin.y+"px");
		} else if(frame.origin.y != null) {
			this.dom.css("bottom", frame.origin.y+"px");
		} else {
			if(frame.origin.bottom != null) {
				this.dom.css("bottom", frame.origin.bottom+"px");
			}
			if(frame.origin.top != null) {
						window.console.error(frame);

				this.dom.css("top", frame.origin.top+"px");
			}
		}
		
		if(frame.origin.x != null) {
			this.dom.css("left", frame.origin.x+"px");
		} else {
			if(frame.origin.left != null) {
				this.dom.css("left", frame.origin.left+"px");
			}
			if(frame.origin.right != null) {
				this.dom.css("right", frame.origin.right+"px");
			}
		}
	}
	
	if(frame.size) {
		if(frame.size.width !== null) {
			this.dom.width(frame.size.width+"px");
		}
		if(frame.size.height !== null) {
			this.dom.height(frame.size.height+"px");
		}
	}
		
	return this; //Allows chaining
},

/*
	Sets the margins for the view. defualt is 0,0,0, format is {top:0, left:0, right:0, bottom}
	any single value can be left out to only set a single margin ie self.setMargin({top:34})..
 */
setMargin:function(margin) {
	if(margin.top) {
		
		if(this.tag !== 'div') {
			this.setDisplay("block");
		}
		
		this.dom.css("margin-top", margin.top+"px");
	}	
	
	if(margin.right)
		this.dom.css("margin-right", margin.right+"px");
	if(margin.left)
		this.dom.css("margin-left", margin.left+"px");
	
	if(margin.bottom) {
		if(this.tag !== 'div') {
			this.setDisplay("block");
		}
		
		this.dom.css("margin-bottom", margin.bottom+"px");
	}
	return this;	//allows chaining
},

setPadding:function(padding) {
	if(padding.top)
		this.dom.css("padding-top", padding.top+"px");
	if(padding.right)
		this.dom.css("padding-right", padding.right+"px");
	if(padding.left)
		this.dom.css("padding-left", padding.left+"px");
	if(padding.bottom)
		this.dom.css("padding-bottom", padding.bottom+"px");
	
	return this;	//allows chaining
},

/*
	Set the display type for the backing dom context
	For example, 
	
	inline, for wrapping
	block for block elements
	...

	When margin-top/bottom is set in setMargin, and tag is not <div> then, display is auto set to block
	
	For more info see: http://www.quirksmode.org/css/display.html
*/	
setDisplay:function(name) {
	this.dom.css("display", name);
},

/*
	Requires the WBColor class for color/gradient objects
*/
setBackground:function(colorOrGradient) {
	if(colorOrGradient.start) {
		//draw gradient
		this.dom[0].style.background = colorOrGradient.gradientValue();	//This automatically handles various platforms
	} else {
		this.dom[0].style.backgroundColor = colorOrGradient.cssValue();
	}
	
	return this;
},

/*	
	Main drawing for a view, this does not need to ever view explcitly be called. However subclasses con overide this
	to implement their own custom drawing. They do not need to call super, however to implement super's drawing logic it can be called.

	View that want to encapsulate subview and drawing logic should add subview here as the context is ready to be 
	manipulated, and the graph will work.
	
	1. Subclasses MUST call super's implementation
*/
draw:function() {	
						   
	if(this.uid) {
		this.dom.attr("id", this.uid);
	}

	//Create the context for subclasses, so they can safly draw within a dom context (aliet hidden by defualt)
	this.enableContext();
	
	//Subclasses should do something interesting here for drawing, they should call super so that their is a proper sketch pad for dealing with the
	//display context, otherise. dom.parent() will be null and not a part of the document tree
	
	//Draw gets called from each of -drawing() for each view in the heirarchy. This can be called for each time a view is drawing or moved to new view.
	//The draw method can also be used to add subviews. This can be used to "contain" a complete drawing invironment. Note that this is better done in init.
	
	
	//Note!!: -draw could be called multiple times for a single view during its view lifespan
},

/*
	This basically insures the the view has a backing context (ie a window/document in its ancestory
*/
enableContext:function() {
	if(!this.dom.parent().length) {
		this.dom.appendTo(document.body)
		.hide();
		
		if(!this._drawsOutOfBounds) {
			this.dom.css("overflow", "hidden");
		}
		
		if(!this.dom.css("position")) {
			this.dom.css("position", "relative");	//default positioning is relative, subclasses can overide with self.setAbsolute()
		}
	}
},

/*
	This gets triggered when a the view changes contexs, if from offscreen to onscrean, 
	of when changed from various views
	
	Subclasses can overide this to do something interesting.
*/
contextWillChange:function(newContext) {

},


contextDidChange:function(newContext) {
	
},

/*
	Returns if the current drawing context for the view is offscreen.
	This occurs when the view's dom is not null and equals $(document.body)
*/
contextIsOffScreen:function() {
	return (this.dom.parent().length>0 && this.dom.parent() === $(document.body));
},

/*
	Main Internal Rendering
	---------------------------
	
	This gets called automatically by the system when views are added to subview that is already attached
	to the document context?
	
	For other cases, users can call -drawing as it return the a jQuery dom object that can be inserted into
	the vanella dom tree with.
	
	return.appendTo(document.body) , or $(someParent).append(myView.drawing());
*/
	
drawing:function() {
	//First draw ourself, this sets up our size, orintation and visual representation
	this.draw();

	
	//First add all child views to this calling their drawing methods to insure that they are properly drawn.
	for(var i in this.subviews) {
		var aview = this.subviews[i];
		
		//Indicate that the context changed
		
		if(!aview.dom) {
			throw "A subview that is not a WBView instance has been added. ["+aview.className+"]";
		}
		
		if(this.dom != aview.dom.parent()) {
			aview.contextWillChange(this.dom);
		}
		
		this.dom.append(aview.drawing());
		if(!aview._isHidden) {
			aview.show();
		}
	}
	
	return this.dom;
},

/*

	View Bindings (Programic, not attribute version)
	------------------------------------------------
	
	attribute, is one of "text", html, .... or extended by subclass in their 
	-observeBindingAction(attribute, object, key), they should call super's implementation

	toObject, the NSObject subclass to bind to.
	
	key, the key to observe changes to.

	altFunction, [optional] can be used to get generated content rather then a key value on
	binding change
	
	Becouse these use inline functions, we use this view as the context for the bindings, doing
	so allows us to unbind using the same context. The context is currently this object's _hash as
*/
bind:function(attribute, toObject, key, altFunctionName) {
	if(toObject.addObserverFunctionForKey) {
		var that = this;
		toObject.addObserverFunctionForKey(function(e){
			that.observeBindingAction(attribute, toObject, key, altFunctionName);
		}, key, this._hash);
	}		
},

//Removes a binding, independing on attribute
unbind:function(key, object) {
	if(object.removeObserverFunctionForKey) {
		object.removeObserverFunctionForKey(null, key, this._hash);
	}
},

observeBindingAction:function(attribute, object, key, altFunctionName) {
	if(attribute === 'html') {
		if(altFunctionName)
			this.dom.html(object[altFunctionName]());
		else
			this.dom.html(object[key]);
	} else if(attribute == 'text') {
		if(altFunctionName)
			this.dom.text(object[altFunctionName]());
		else
			this.dom.text(object[key]);
	}
}

}});

/*

	WBListView
	-------------
	
	Provides a simple table view for laying out list of rows with no columnar support
	
	WBListView uses a delegate/datasource system for populating the rows of its list view.
	The delegate methods can be implemented by a custom object or a WBArrayController instance
	can be used as a delegate (as it implements the needed list delegate methods
	
	Delegate Methods
	//Required
	- (int)listViewNumberOfRows(WBListView listview);
	- (Object)listViewObjectForRowAtIndex(WBListView listview, int index);
	
	//Optional
	- (WBListViewCell)listViewCellForRowAtIndexWithObject(WBListView listview, index, Object object); 
	If not implemented then WBView.cellPrototype Class's initWithObject(rowObject) is used to initalize
	
	- (void)listViewClickRowAtIndex(WBListView listView, int index);

*/
// !WBListView
fClass.WBView.extend("WBListView", { ivars:{
	rows:[],			//Cached rows for the view
	selectedRow:null,
	delegate:null,		//See notes above, simply use listView.delegate = aDelegate
	cellPrototype:null	//A Class object subclassed from WBListViewCell
}, methods: {
	
init:function() {
	if(/*this.performSuper(this, "initWithTag")*/this.initWithTag()) { //uses defualt of <div>
	}
	return this;
},

addRow:function(aListViewCell, animation, speed) {
	this.rows.push(aListViewCell);
	this.addSubview(aListViewCell, animation, speed);	//This handles a draw call
},

removeRow:function(aListViewCell, animation, speed) {
	this.removeRowAtIndex(indexof(aListViewCell, this.rows), animation, speed);
},

removeRowAtIndex:function(index, animation, speed) {
	var removalRow = this.rows[index];
	removalRow.removeFromSuperView(animation, speed);
	this.rows = removefrom(removalRow, this.rows);
	
	//Select new row if current section was this
	if(this.selectedRow === removalRow) {
		// TODO: Select previous row
	}
},

draw:function() {
	this.performSuper(this, "draw");
	
	//Clean up old, could be made more efficient
	this.removeAllSubviews();
	this._cacheRowsFromDelegate();
	for(var i in this.rows) {
		this.addSubview(this.rows[i]._cellViewWithParent(this));
	}
},

/*
	Internal
	----------

	refreshes the .row with rows processed from the delegate
*/
_cacheRowsFromDelegate:function() {
	var rows = this.delegate.listViewNumberOfRows(this);
	
	//Clear out old row cache
	this.rows = [];
	
	//NSLog("Should cache tableview rows:"+rows);
	for(var i = 0; i<rows; i++) {
		var rowObject = this.delegate.listViewObjectForRowAtIndex(this, i)
		
		var cell;
		if(this.delegate.listViewCellForRowAtIndexWithObject) {
			cell = this.delegate.listViewCellForRowAtIndexWithObject(this, i, rowObject);
		} else {
			cell = this.cellPrototype.alloc().initWithObject(rowObject);
		}
		cell.setId(i.toString());
		this.rows.push(cell);
	}
},

_clickForCell:function(aCell) {
	if(this.delegate.listViewClickRowAtIndex) {
		this.delegate.listViewClickRowAtIndex(this, indexof(aCell, this.rows));
	}
},

_dblClickForCell:function(aCell) {
	if(this.delegate.listViewDblClickRowAtIndex) {
		this.delegate.listViewDblClickRowAtIndex(this, indexof(aCell, this.rows));
	}
},
	
}});

/*

	WBListViewCell
	----------------
	
	The template cell for a list view
	
*/
// !WBListViewCell
fClass.WBView.extend("WBListViewCell", { ivars: {
	parent:null,	//The parent listview for this cell
	object:null		//The object for this row, should be a bindable object
}, methods : {

/*
	Designated Initalizer
	--------------------
	
*/
initWithObject:function(anObj) {
	if(/*this.performSuper(this, "initWithTag")*/this.initWithTag()) {
		this.object = anObj;
		
		//Actions
		var that = this;
		this.dom.hover(function(e) {
			that.mouseEntered(e);
		}, function(e) {
			that.mouseExited(e);
		});
		
		this.dom.click(function(e) {
			if(that.parent) {
				that.parent._clickForCell(that);
			}
		});
		
		this.dom.dblclick(function(e) {
			if(that.parent) {
				that.parent._dblClickForCell(that);
			}
		});
	}
	return this;
},

/*
	Responder Actions
	--------------------------
	Subclasses that overide must call super's impl
*/
mouseEntered:function(event) {

},

mouseExited:function(event) {

},

/*
	private used by WBListView
	--------------------------
*/
_cellViewWithParent:function(parent) {
	this.parent = parent;
	return this;	
}
	
}});

/*

	Exparimental Views and Controls
	=================================
	
	These are not finalized, they may be excluded from final version of froth.uikit.js
	
	Exposed Bindable Attributes
	----------------------------
	
	- title	//use -bind("title", object, objectKey)
	
*/

// !WBLinkable : WBView
/*	Provides a linkable superclass for adding link attributes to headers and other items
	-initWithTag(tag|null),
	
	Provides psuodo onclick, or href support to views (this is meant as a mini widget)
 */
fClass.WBView.extend("WBLinkable", { ivars:{
	_innerDOM:null	//may be same as this.dom, or may be a inner wrapped <a/>
}, methods:{

initWithTag:function(tagOrAnchor, idOrNull) {
	if(this.performSuper(this, "initWithTag", [tagOrAnchor?tagOrAnchor:"a", idOrNull])) {
		this.dom.append("<span>");
	}
	return this;		
},

setHref:function(href) {
	if(!href) {
		throw "This is a problem";
	}

	NSLog('should set href to:'+href);
	if(this.tag != "a") { 
		if(!this.dom.find("span a").length) {
			this.dom.find("span").wrapInner("<a href='"+href+"'></a>");
			this._innerDOM = this.dom.find("span a");
		} else {
			this.dom.find("span a").attr('href', href);
		}
	} else {
		this.dom.attr("href", this.href);
	}
	this.href = href;
	
	return this;
},

setTitle:function(title) {
	this.dom.find("span").html(title);
		
	if(this.href) {
		this.setHref(this.href);
	}
	
	return this;
},

setAction:function(aFunc) {
	this.action = aFunc;		
	return this;
},

//Overidable by subclasses for custom didClick events
didClick:function() {
	if(this.action) {
		this.action(this, this.dom.attr("id"));
	}
	return this;
},

draw:function() {
	this.performSuper(this, "draw");
		
	/*if(this.testValue) {
		NSLog("FRLinkable -draw("+this.className+") tag:"+this.tag + " tv:"+this.testValue);
	}*/
	
	if(this.title) {
		this.setTitle(this.title);
	} else if(this.href) {
		this.setHref(this.href);
	}
	
	//We need to insure that the linkable has a pointer. if their is no href attribute then we use css curser: pointer;
	if((this.action) || !(this.dom.attr("href") || !this.dom.find("a").attr("href"))) {
		this.dom.css("cursor", "pointer");
	}
	
	//Prepare actions
	var me = this;
	this.dom
	.click(function(e) {
		   me.didClick();
		   if(me.action) {
		 	 	e.stopPropagation();
		   		return false;
		   	}
	});
},

//Bindings support, we can be bound to "title" for dynmic updates when bound object changes
observeBindingAction:function(attribute, object, key) {
	if(attribute === 'title') {
		this._innerDOM.html(object[key]);
	}
}
	
}});

// !WBLink : WBLinkable
/*
	WBLink
	==============
*/
fClass.WBLinkable.extend("WBLink", { ivars:{
	// .. no ivars ..
}, methods: {
	
//Designated initalizer
initWithTitle:function(title, hrefOrNull, idOrNull) {
	//this.performSuper(this, "initWithTag", ["a", idOrNull]);
	this.initWithTag("a", idOrNull);
	
	//For tests
	//this.testValue = "1292";
	
	this.setTitle(title);
	
	if(hrefOrNull) {
		this.setHref(hrefOrNull);
	}
	
	return this;
},

//Just for tests
draw:function() {
	this.performSuper(this, "draw");
}

}});

// !WBSectionTitle
/*
	WBSectionTitle
	==============
*/
fClass.WBLinkable.extend("WBSectionTitle", { ivars:{
	// .. no ivars ..
}, methods: {

initWithTitle:function(title, hrefOrNull, idOrNull) {
	//this.performSuper(this, "initWithTag", ["h3", idOrNull]);
	this.initWithTag("h3", idOrNull);
	
	this.setTitle(title);
	
	if(hrefOrNull)
		this.setHref(hrefOrNull);
	
	return this;
}	

}});

// !WBHeader : WBLinkable
/*
	WBHeader
	==============
*/
fClass.WBLinkable.extend("WBHeader", { ivars:{
	// .. no ivars ..
}, methods: {

initWithTitle:function(title, hrefOrNull, idOrNull) {
	//this.performSuper(this, "initWithTag", ["h1", idOrNull]);
	this.initWithTag("h1", idOrNull);
	
	this.setTitle(title);
	
	if(hrefOrNull)
		this.setHref(hrefOrNull);
	
	return this;
}	

}});

/*
	WBProgressView
	-----------------
	
	Provides a spinner (or in future a bar) progress indicator view
	for display
	
*/
fClass.WBView.extend("WBProgressView", { ivars : {
	img:null,
}, methods : {

init:function() {
	if(/*this.performSuper(this, "initWithTag", ["img"])*/ this.initWithTag('img')) {
		this.dom.attr("src", "http://www.socialproductivity.net/img/a_load_mini.gif");
	}
	return this;
}

}});

/*

	WBControl
	---------
	
	An Abstract base class for textfields, buttons and other control views.
	
	Discussion
	-----------
	
	Controls provide a target/action mechanism for handling their events, for some controls this
	event may be a click, for others it may be the return key.
	
*/
// !WBControl : WBView
fClass.WBView.extend("WBControl", { ivars : {
	target:null,			//the target for action methods.
	action:null,			//the method name to fire for 
	stringValueProxy:null	//Proxy object set with takeStringValueFrom:
}, methods : {
	
initWithTag:function(tag, idOrNull) {
	if(this.performSuper(this, "initWithTag", arguments)) {
		
		var that = this;
		//Does not use nsresponder's insertNewline implementation, but an internal impl
		var eventAction = this.eventForAction();
		if(eventAction === 'insertNewline' || eventAction === 'return') {
			this.dom.keyup(function(e) {
				if(e.keyCode == 13) {
					if(that.target) {
						that.target[that.action](that, e);
						e.stopPropagation();
						return false;
					}
				} 
			});
		} else {
			this.dom.bind(eventAction, function(e) {
				/*
					Controls never automatically propigate
				*/
				if(that.target) {
					that.target[that.action](that, e);
					e.stopPropagation();
					return false;
				}
			});
		}
	}
	return this;
},


/*
	Target/Action Mechanizm
	--------------------------
	
*/
setTarget:function(aTarget) {
	this.target = aTarget;
},

setAction:function(aMethodName) {
	this.action = aMethodName;
},

setTargetAction:function(target, action) {
	this.setTarget(target);
	this.setAction(action);
},

/*
	Defualt: click / mouseUp
	Subclasses can overide this to send action on other responder events,
	
	returns a string one of.
	-keyUp
	-insertNewline
	-mouseUp
	-mouseDown
	-mouseEntered
	-mouseExited
	...
	or any dom event , click, hover...
*/
	
eventForAction:function() {
	return "click";
},

/*
	Value Getting and Setting
	--------------------------
	
*/
//Used for bindings and other things, defualt is stringValue, subclasses can provide another object value
objectValue:function() {
	return this.stringValue();
},

setObjectValue:function(anObj) {
	this.setStringValue(anObj);
},

//object must implement .stringValue()
takeStringValueFrom:function(control) {
	this.stringValueProxy = control;
},

stringValue:function() {
	if(this.stringValueProxy) {
		return this.stringValueProxy.stringValue();
	}

	if(this.tag === 'input' || this.tag === 'button' || this.tag === 'textarea') {
		return this.dom.val();
	} else {
		return this.dom.text();
	}
},

setStringValue:function(val) {
	if(this.stringValueProxy) {
		this.stringValueProxy.setStringValue(val);
	} else {	
		if(this.tag === 'input' || this.tag === 'button' || this.tag === 'textarea') {
			this.dom.val(val);
		} else {
			this.dom.text(val);
		}
	}
},

performAction:function() {
	this.target[this.action](this);
},

}});


/*
	Text Field
	----------
	
	Matches the global application style for text fields. 

	TextFields provide responder support for insertNewline for handling enter key (TextFields dont support enter key...
	
	Also provides support for a "placeholder" if configured
	
	TextFields do not use normal html form post logic, and are handle interly by the WBTextField class

	html context : <input type="text">
*/
// !WBTextField : WBControl	
fClass.WBControl.extend("WBTextField", { ivars : {
	
}, methods : {

init:function() {
	if(/*this.performSuper(this, "initWithTag", ["input"])*/this.initWithTag("input")) {
		this.dom.attr("type", "text");
	}
	return this;
},

//Adds the .placeholder class for drawing placeholder style
setPlaceholderString:function(placeholder) {
	if(!this.placeholder && this.placeholder !== placeholder) {
		this.placeholder = placeholder;
		this.dom.val(this.placeholder);
		this.addCSSClass("placeholder");
	}
},

becomeFirstResponder:function(e) {
	if(this.placeholder === this.stringValue()) {
		this.dom.val("");
		this.removeCSSClass("placeholder");
	}
},

resignFirstResponder:function(e) {
	if(!this.stringValue() || !(this.stringValue().length>0)) {
		this.dom.val(this.placeholder);
		this.addCSSClass("placeholder");
	}
},

// As WBControl
eventForAction:function() {
	return "insertNewline";
},

//A name attribute for  form items
setName:function(aName) {
	this.dom.attr("name", aName);
},

name:function() {
	return this.dom.attr("name");
},

}});

/*
	WBTextArea
	---------------
	
	Provides a multi line text area, this is not the same as a textview
	that provides rich text features
*/	
// !WBTextArea : WBTextField
fClass.WBTextField.extend("WBTextArea", { ivars : {

}, methods: {

init:function() {
	if(/*this.performSuper(this, "initWithTag", ["textarea"])*/this.initWithTag("textarea")) {
		
	}	
	return this;
},

setRows:function(anInt) {
	this.dom.attr("rows", anInt.toString());
}
	
}});

/*
	Provides a simple button control
	
	html context 	: <button>
	layout			: inline
	
*/
// !WBButton : WBControl
fClass.WBControl.extend("WBButton", { ivars :{

}, methods :{

init:function() {
	if(/*this.performSuper(this, "initWithTag", ["button"])*/this.initWithTag('button')) {
		this.setDisplay("inline");
	}
	return this;
},

setTitle:function(aTitle) {
	//this.setStringValue(aTitle);
	this.dom.text(aTitle);
},

title:function() {
	//return this.stringValue();
	return this.dom.text();
}

}});

// !WBButtonLink : WBButton
fClass.WBButton.extend("WBButtonLink", { ivars :{

}, methods: {

init:function() {
	if(this.performSuper(this, "initWithTag", ["a"])) {
		this.setDisplay("inline");
		this.dom.css("cursor", "pointer");
	}
	return this;
}

}});


/*
	
	Forms
	================

	
	Standard Behaviour
	-------------------
	
	The standard behaviour is not really standard as the form does not
	do any post/get methods, however it creates the "Submit" and "Cancel" 
	(or custom, accesable at this.submitButton, this.cancelButton) 
	
	Delegate methods
	- formDidCancel(form)
	- formDidSubmit(form)
	
*/
// !---------------
// !WBForm : WBView
fClass.WBView.extend("WBForm", { ivars : {
	contentView:null, 	//The inner fieldset content view
	delegate:null		//Optional delegate to receive standard behaviour delegate actions
}, methods: {

init:function() {
	if(/*this.performSuper(this, "initWithTag", ["form"])*/ this.initWithTag('form')) {
		this.contentView = fClass.WBView.alloc().initWithTag("fieldset");
		this.addSubview(this.contentView);
	}
	return this;
},

createTextFieldWithName:function(name, optLabel, optPlaceholder) {
	var textField = fClass.WBTextField.alloc().init();
	if(optPlaceholder) {
		textField.setPlaceholderString(optPlaceholder);
	}
	textField.setName(name);

	if(optLabel) {
		var labelView = fClass.WBFormLabel.alloc().init();
		labelView.setStringValue(optLabel);
		labelView.setAssociatedField(textField);
		this.contentView.addSubview(labelView);
		
		textField.labelView = labelView;
	}
	
	this.contentView.addSubview(textField);
	
	return textField;
},

//WBTextArea
createTextAreaWithName:function(name, optLabel) {
	var textArea = fClass.WBTextArea.alloc().init();
	textArea.setName(name);

	if(optLabel) {
		var labelView = fClass.WBFormLabel.alloc().init();
		labelView.setStringValue(optLabel);
		labelView.setAssociatedField(textArea);
		this.contentView.addSubview(labelView);
		
		textArea.labelView = labelView;
	}
	
	this.contentView.addSubview(textArea);
	
	return textArea;
},

addTextFieldWithName:function(textField, name, optLabel) {
	if(name) {
		textField.setName(name);
	}
	
	if(optLabel) {
		var labelView = fClass.WBFormLabel.alloc().init();
		labelView.setStringValue(optLabel);
		labelView.setAssociatedField(textField);
		this.contentView.addSubview(labelView);
		
		textField.labelView = labelView;
	}
	
	this.contentView.addSubview(textField);
	
	return textField;
},

createButton:function(title, target, action) {
	if(!this.buttons) {
		this.buttons = fClass.WBView.alloc().initWithTag("fieldset");
		this.buttons.setDisplay("block");
		this.contentView.addSubview(this.buttons);
	}

	var button = fClass.WBButton.alloc().init();
	this.buttons.addSubview(button);
	
	button.setTitle(title);
	button.setTargetAction(target, action);
	
	return button;
},

/*
	Standard submit/cancel actions
*/
useStandardBehaviour:function(optSubmitTitle, optCancelTitle) {
	if(!this.submitButton && !this.cancelButton) {
		this.submitButton = this.createButton(optSubmitTitle?optSubmitTitle:"Submit", this, 'submitAction');
		this.cancelButton = this.createButton(optCancelTitle?optCancelTitle:"Cancel", this, 'cancelAction');	
	}
},

submitAction:function(sender, e) {
	this.delegate.formDidSubmit(this);
},

cancelAction:function() {
	this.delegate.formDidCancel(this);
},

/*
	Getting form data as an object
	
	Subclasses can overide and call super to add extra data.
*/
formData:function() {
	//TODO: how do we get a html form data, its not in HTMLFormElement specification
},

}});


// !WBFormLabel : WBView
fClass.WBControl.extend("WBFormLabel", { ivars : {

}, methods :{ 

init:function() {
	if(/*this.performSuper(this, "initWithTag", ['label'])*/this.initWithTag('label')) {
		this.dom.css("display", "");	//Allow css file to edit
	}
	return this;
},

//aField must return a value for it's "name"
setAssociatedField:function(aField) {
	this.dom.attr("for", aField.name());
}

}});
